// *********************************************************
//      Copyright (c) Kosta Gaitanis
//      All rights reserved
//
//      k.gaitanis@gmail.com
//
// *********************************************************

#include "Vector.h"

// Predefined constant vectors
const Vector Vector::ORTHO_X(1.0, 0.0, 0.0);
const Vector Vector::ORTHO_Y(0.0, 1.0, 0.0);
const Vector Vector::ORTHO_Z(0.0, 0.0, 1.0);
const Vector Vector::ZERO(0.0, 0.0, 0.0);

Vector::Vector() :
    m_isEmpty(true)
{
}

Vector::Vector(double x, double y, double z)
{
    setCoordinates(x, y, z);
}

Vector::Vector(const Vector &other)
{
    setCoordinates(other.getGLCoordinates());
    m_isEmpty = other.m_isEmpty;
}

bool Vector::operator ==(const Vector &other) const
{
    //START//
    // two empty vectors are equal
    // an empty vector is never equal to a non-empty vector (even if it is (0,0,0)
    // two non-empty vectors are equal only if x1=x2,y1=y2 and z1=z3
    if (m_isEmpty && other.m_isEmpty) {
        return true;
    }
    return ( (m_isEmpty == other.m_isEmpty) &&
             (compare(m_coordinates[0], other.m_coordinates[0])) &&
             (compare(m_coordinates[1], other.m_coordinates[1])) &&
             (compare(m_coordinates[2], other.m_coordinates[2])) );
    //INSERT//return false;
    //END//

}

bool Vector::operator !=(const Vector &other) const
{
    return !(Vector::operator ==(other));
}

Vector &Vector::operator =(const Vector &other)
{
    setCoordinates(other.getGLCoordinates());
    m_isEmpty = other.m_isEmpty;
    return *this;
}

Vector Vector::operator +(const Vector &other) const
{
    //START//
    // add the two vectors and return a new vector
    Vector sum(m_coordinates[0] + other.m_coordinates[0],
               m_coordinates[1] + other.m_coordinates[1],
               m_coordinates[2] + other.m_coordinates[2]);

    return sum;
    //INSERT//return Vector();
    //END//
}

Vector Vector::operator -(const Vector &other) const
{
    //START//
    // subtract the two vectors and return a new vector
    Vector difference(m_coordinates[0] - other.m_coordinates[0],
                      m_coordinates[1] - other.m_coordinates[1],
                      m_coordinates[2] - other.m_coordinates[2]);

    return difference;
    //INSERT//return Vector();
    //END//
}

Vector& Vector::operator +=(const Vector &other)
{
    //START//
    // add the other vector to this one and return this vector
    // modify the private variable m_coordinates
    m_coordinates[0] += other.m_coordinates[0];
    m_coordinates[1] += other.m_coordinates[1];
    m_coordinates[2] += other.m_coordinates[2];
    //END//
    return *this;
}

Vector& Vector::operator -=(const Vector &other)
{
    //START//
    // subtract the other vector from this one and return this vector
    // modify the private variable m_coordinates
    m_coordinates[0] -= other.m_coordinates[0];
    m_coordinates[1] -= other.m_coordinates[1];
    m_coordinates[2] -= other.m_coordinates[2];
    //END//
    return *this;
}

double Vector::operator *(const Vector &v) const
{
    //START//
    // scalar product between two vectors returns a single number
    return m_coordinates[0] * v.m_coordinates[0] +
           m_coordinates[1] * v.m_coordinates[1] +
           m_coordinates[2] * v.m_coordinates[2];
    //INSERT//return 0;
    //END//
}

Vector Vector::operator *(double f) const
{
    //START//
    // multiply this vector by a scalar (number) and return the new vector
    return Vector(f * m_coordinates[0],
                  f * m_coordinates[1],
                  f * m_coordinates[2]);
    //INSERT//return Vector();
    //END//
}

Vector Vector::operator ^(const Vector &other) const
{
    //START//
    // Vectorial product between two vectors and return the resulting vector
    return Vector(m_coordinates[1] * other.m_coordinates[2] - m_coordinates[2] * other.m_coordinates[1],
                  m_coordinates[2] * other.m_coordinates[0] - m_coordinates[0] * other.m_coordinates[2],
                  m_coordinates[0] * other.m_coordinates[1] - m_coordinates[1] * other.m_coordinates[0]);
    //INSERT//return Vector();
    //END//
}

Vector Vector::operator -() const
{
    //START//
    // return a new vector that has the negative values of this one
    return Vector(-m_coordinates[0], -m_coordinates[1], -m_coordinates[2]);
    //INSERT//return Vector();
    //END//
}

bool Vector::compare(GLdouble a, GLdouble b, GLdouble e)
{
    //START//
    // checks if the difference between a and b is smaller than e
    GLdouble d = a - b;
    return ( (d < e) && (d > -e) );
    //INSERT//return false;
    //END//
}

GLdouble Vector::getLength() const
{
    //START//
    // the length of a vector is a number
    return sqrt(m_coordinates[0]*m_coordinates[0] +
                m_coordinates[1]*m_coordinates[1] +
                m_coordinates[2]*m_coordinates[2]);
    //INSERT//return 0;
    //END//
}

Vector Vector::normalized() const
{
    //START//
    // return a new vector that has the same direction as this one but has
    // a length of 1
    // divide all the components of this vector by its length
    // to normalize it (be careful with divide by 0 !).
    // modify the private variable m_coordinates of the new vector to store the result
    Vector n(*this);
    GLdouble length = n.getLength();

    if (length == 0.0) return Vector(0, 0, 0);

    n.m_coordinates[0] /= length;
    n.m_coordinates[1] /= length;
    n.m_coordinates[2] /= length;
    return n;
    //INSERT//return Vector();
    //END//
}

void Vector::setCoordinates(const GLdouble *coordinates)
{
    setCoordinates((double) coordinates[0], (double) coordinates[1], (double) coordinates[2]);
}

void Vector::setCoordinates(double x, double y, double z)
{
    m_isEmpty = false;

    m_coordinates[0] = (GLdouble) x;
    m_coordinates[1] = (GLdouble) y;
    m_coordinates[2] = (GLdouble) z;
}

const GLdouble *Vector::getGLCoordinates() const
{
    return m_coordinates;
}

bool Vector::isEmpty() const
{
    return m_isEmpty;
}

double Vector::x() const
{
    return (double) m_coordinates[0];
}

double Vector::y() const
{
    return (double) m_coordinates[1];
}

double Vector::z() const
{
    return (double) m_coordinates[2];
}

void Vector::setX(double x)
{
    m_coordinates[0] = (GLdouble) x;
}

void Vector::setY(double y)
{
    m_coordinates[1] = (GLdouble) y;
}

void Vector::setZ(double z)
{
    m_coordinates[2] = (GLdouble) z;
}

QDebug operator << (QDebug dbg, const Vector &vector)
{
    dbg.nospace() << "Vector (" << vector.x()
                  << ", " << vector.y()
                  << ", " << vector.z() << ")\r\n";

    return dbg.space();
}




